#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <assert.h>
#include <sys/types.h>
#include <netdb.h>           // For gethostbyname()


#include "mini.kit/socket.h"

//extern int errno;

namespace MINIKIT
{

SocketException::SocketException( const std::string& msg, bool syserr ): m_message(msg)
{
    if( syserr )
    {
        m_message = m_message + " : " + std::string( strerror(errno) );
    }
    
}

SocketException::~SocketException() throw()
{}

const char* SocketException::what() const throw() 
{
    if (m_message.empty()) 
        return "Socket Exception.";
    else
        return m_message.c_str();
}


//------------------------------
//class Socket
//
Socket::~Socket()
{
    if(this->fd>=0)
    {
        ::close(this->fd);
        this->fd = -1;
    }
    if(this->sa_local!=NULL)
    {delete this->sa_local; this->sa_local=NULL;}
    if(this->sa_remote!=NULL)
    {delete this->sa_remote; this->sa_remote=NULL;}
}

Socket::Socket(int32_t type, int32_t protocol) throw(SocketException)
{
    //this->so_domain    = domain;
    this->so_type      = type;
    this->so_protocol  = protocol;
    this->sa_local     = NULL;
    this->sa_remote    = NULL;
    this->fd = -1;
    this->setTimeout( 6, 12 );
}

Socket::Socket(int32_t fd)
{
    this->sa_local  = NULL;
    this->sa_remote = NULL;
    this->fd = fd;
    this->setTimeout( 6, 0 );
}

void  Socket::setNonBlock()
{
    int32_t val = ::fcntl(fd, F_GETFL, 0);
    ::fcntl(this->fd, F_SETFL, val|O_NONBLOCK);
}

void  Socket::setReuseAddr()
{
    int32_t   opt_val = 1;
    socklen_t opt_len = 4;
    ::setsockopt(this->fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, opt_len);
}

void  Socket::setLinger(bool on, int32_t sec_delay)
{
    struct linger l = {on, sec_delay};
    ::setsockopt(this->fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
}

void  Socket::setTimeout(size_t sec, size_t usec)
{
    this->setConnTimeout( sec, usec );
    this->setRecvTimeout( sec, usec );
}

void  Socket::setConnTimeout(size_t sec, size_t usec)
{
    timeval &tv = this->tm_conn;
    tv.tv_sec  = sec;
    tv.tv_usec = usec;
}

void  Socket::setRecvTimeout(size_t sec, size_t usec)
{
    timeval &tv = this->tm_recv;
    tv.tv_sec  = sec;
    tv.tv_usec = usec;
    if(this->fd>=0)
    {
        ::setsockopt( this->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) );
    }
}

const sockaddr& Socket::getLocalSA() throw(SocketException)
{
    if(this->sa_local!=NULL)
        return *(this->sa_local);
    this->sa_local   = new sockaddr();
    socklen_t sa_len = sizeof(sockaddr);
    if (::getsockname(this->fd, this->sa_local, &sa_len) < 0) 
    {
        delete this->sa_local; this->sa_local = NULL;
        throw SocketException("getLocalSA, ::getsockname failed.", true);
    }
    return *(this->sa_local);
}

const sockaddr& Socket::getRemoteSA() throw(SocketException)
{
    if(this->sa_remote!=NULL)
        return *(this->sa_remote);
    this->sa_remote  = new sockaddr();
    socklen_t sa_len = sizeof(sockaddr);
    if (::getpeername(this->fd, this->sa_remote, &sa_len) < 0) 
    {
        delete this->sa_remote; this->sa_remote = NULL;
        throw SocketException("getRemoteSA, ::getpeername failed.", true);
    }
    return *(this->sa_remote);
}

std::string Socket::getLocalAddr() throw(SocketException)
{
    return Socket::toAddr( this->getLocalSA() );
}

int32_t     Socket::getLocalPort() throw(SocketException)
{
    return Socket::toPort( this->getLocalSA() );
}

std::string Socket::getRemoteAddr() throw(SocketException)
{
    return Socket::toAddr( this->getRemoteSA() );
}

int32_t     Socket::getRemotePort() throw(SocketException)
{
    return Socket::toPort( this->getRemoteSA() );
}

void*   Socket::toINAddr(const sockaddr &sa)
{
    if (sa.sa_family == AF_INET) {
        return &(((sockaddr_in*)&sa)->sin_addr);
    }
    return &(((sockaddr_in6*)&sa)->sin6_addr);
}

uint16_t Socket::toINPort(const sockaddr &sa)
{
    if (sa.sa_family == AF_INET) {
        return ((sockaddr_in*)&sa)->sin_port;
    }
    return ((sockaddr_in6*)&sa)->sin6_port;
}

std::string Socket::toAddr(const sockaddr &sa)
{
    char ip[INET6_ADDRSTRLEN] = {0};
    if(NULL==inet_ntop(sa.sa_family, toINAddr(sa), ip, INET6_ADDRSTRLEN))
        return "";
    else
        return ip;
}

uint16_t    Socket::toPort(const sockaddr &sa)
{
    return ntohs(toINPort(sa));
}

void Socket::setupSocket_DO( const std::string& addr, int32_t port, FUNC_SOCK_ACTION func_action, sockaddr &sa, socklen_t &sa_len, bool is_passive ) throw(SocketException)
{
    //is_passive   
    int32_t ret = 0;
    struct addrinfo hints;
    struct addrinfo *servinfo, *p;
    char buf_port[16] = {0};

    //通过getaddrinfo获得: 可用的协议簇/套接字类型/地址等信息
    ::snprintf( buf_port, sizeof(buf_port), "%d", port);
    ::memset( &hints, 0x00, sizeof(hints) );
    hints.ai_family   = AF_UNSPEC;  //ipv4 or ipv6
    hints.ai_socktype = this->so_type;
    hints.ai_protocol = this->so_protocol;
    if( (addr.empty()||addr=="*") && is_passive )
    {
        hints.ai_flags = AI_PASSIVE; 
        ret = ::getaddrinfo( NULL, buf_port, &hints, &servinfo );
    }
    else
    {
        ret = ::getaddrinfo( addr.c_str(), buf_port, &hints, &servinfo );
    }

    if( ret != 0 ) 
        throw SocketException("::getaddrinfo failed.", true);

    //遍历addrinfo结果列表
    //尝试创建socket以及相应动作(::bind/::connect)
    for( p=servinfo; p!=NULL; p=p->ai_next)
    {
        if( this->fd < 0)
        {
            if( p->ai_family == PF_INET ) 
                fprintf(stderr, "PF_NET\n" );
            else if( p->ai_family == PF_INET6 )
                fprintf(stderr, "PF_NET6\n" );
            else
                fprintf(stderr, "UNKNOWN\n" );


            if( (this->fd = ::socket(p->ai_family, p->ai_socktype, p->ai_protocol))<0 ) 
                continue;
            this->setReuseAddr();
            if(!is_passive)
            {
                timeval &tv = this->tm_recv;
                this->setRecvTimeout(tv.tv_sec, tv.tv_usec);
            }
        }

        if ( func_action(this->fd, p->ai_addr, p->ai_addrlen, this ) == 0 )
        {
            ::memcpy( &sa,       p->ai_addr,     sizeof(sockaddr) );
            ::memcpy( &sa_len, &(p->ai_addrlen), sizeof(socklen_t) );
            break;
        }
        ::close(this->fd);
        this->fd = -1;
    }

    if( p==NULL )
    {
        ::freeaddrinfo( servinfo );
        throw SocketException("::action failed.", true);
    }
    else
    {
        ::freeaddrinfo( servinfo );
    }
}

//------------------------------
//class TCPSocket: public Socket
/*
bool    TCPSocket::isActive()
{
    return this->is_active;
}
*/

TCPSocket::TCPSocket() throw(SocketException): Socket(SOCK_STREAM, IPPROTO_TCP)
{
}

TCPSocket::TCPSocket(int32_t fd): Socket(fd)
{
}

int32_t TCPSocket::send( const void* buf, size_t len ) throw(SocketException)
{
    //TODO: if nonblocking should not throw exception? 
    int32_t isent = 0;
    if( (isent = ::send( this->fd, buf, len, 0))<=0 )
        throw SocketException("::send failed.", true);
    return isent;
}

int32_t TCPSocket::recv( void* buf, size_t len ) throw(SocketException)
{
    //TODO: if nonblocking should not throw exception? 
    int32_t irecv = 0;
    if( (irecv = ::recv( this->fd, buf, len, 0))<=0 )
        throw SocketException("::recv failed.", true);
    return irecv;
}

int32_t TCPSocket::close()
{
    ::close(this->fd);
}

//----------------
//class TCPListener
TCPListener::TCPListener() throw(SocketException): Socket(SOCK_STREAM, IPPROTO_TCP)
{
}

int32_t   TCPListener::bind(const std::string &addr, int32_t port) throw(SocketException)
{
    sockaddr  sa;
    socklen_t sa_len;
    this->setupSocket_DO( addr, port, Socket::bind_wrap, sa, sa_len, true ); 
    return 0;
}

int32_t   TCPListener::listen(size_t maxpending) throw(SocketException)
{
    if ( ::listen(this->fd, maxpending) < 0 )
        throw SocketException("::listen failed.", true);
    return 0;
}

TCPServerSocket* TCPListener::accept() throw(SocketException)
{
    sockaddr_in remote_addr;
    socklen_t   remote_addr_len;

    int32_t connfd;
    while(true)
    {
        connfd = ::accept(this->fd, 0, 0);
        if( connfd<0 )
        {
            if(errno == EINTR)
            {
                continue;
            }
            throw SocketException("::accept failed.", true);
        }
        break;
    }
    return new TCPServerSocket(connfd);
}

//----------------------------
//class TCPServerSocket
//
TCPServerSocket::TCPServerSocket() //should not create socket.
{
    this->fd=0; 
}
TCPServerSocket::TCPServerSocket(int32_t fd)
{
    this->fd = fd;
}

//----------------------------
//class TCPClientSocket: public TCPSocket 
TCPClientSocket::TCPClientSocket() throw(SocketException): TCPSocket()
{
}

int32_t TCPClientSocket::connect(const std::string &addr, int32_t port) throw(SocketException)
{
    sockaddr  sa;
    socklen_t sa_len;
    //this->setupSocket_DO( addr, port, ::connect, sa, sa_len, false ); 
    this->setupSocket_DO( addr, port, Socket::connect_timeout_wrap, sa, sa_len, false );
    return 0;
}


UDPSocket::UDPSocket() throw(SocketException): Socket(SOCK_DGRAM, IPPROTO_UDP)
{
}

UDPSocket::UDPSocket(int32_t fd): Socket(fd)
{
}

int32_t UDPSocket::sendTo( const void* buf, size_t len, const sockaddr *remote_sa, socklen_t remote_sa_len) throw(SocketException)
{
    int32_t ret = 0;
    if ( (ret=::sendto(this->fd, buf, len, 0, remote_sa, remote_sa_len)) < 0) {
        throw SocketException("::sendto failed.", true);
    }
    return ret;
}

int32_t UDPSocket::recvFrom( void* buf, size_t len, sockaddr *remote_sa, socklen_t *remote_sa_len ) throw(SocketException)
{
    int32_t ret = 0;
    if ((ret=::recvfrom(this->fd, buf, len, 0, remote_sa, remote_sa_len )) < 0) {
        throw SocketException("::recvfrom failed.", true);
    }
    return ret;
}

int32_t UDPSocket::sendTo( const void* buf, size_t len, const std::string &remoteAddr, int32_t remotePort ) throw(SocketException)
{
    int32_t ret = 0;
    sockaddr  sa;
    socklen_t sa_len = sizeof(sa);
    this->setupSocket_DO( remoteAddr, remotePort, Socket::func_sock_nothing, sa, sa_len, false ); 
    ret = this->sendTo( buf, len, &sa, sa_len );
}

int32_t UDPSocket::recvFrom( void* buf, size_t len, std::string &remoteAddr, int32_t &remotePort ) throw(SocketException)
{
    int32_t     ret = 0;
    sockaddr    sa;
    socklen_t   sa_len = sizeof(sa);
    ::memcpy( &sa, 0x00, sizeof(sa) );
    ret = this->recvFrom( buf, len, &sa, &sa_len);
    remoteAddr = Socket::toAddr(sa);
    remotePort = Socket::toPort(sa);
    return ret;
}


int32_t UDPServerSocket::bind(const std::string &addr, int32_t port) throw(SocketException)
{
    sockaddr  sa;
    socklen_t sa_len;
    this->setupSocket_DO( addr, port, Socket::bind_wrap, sa, sa_len, true ); 
    return 0;
}

int32_t Socket::func_sock_nothing( int32_t fd, const sockaddr* sa, socklen_t sa_len, Socket* cls)
{
    fprintf(stderr,"Socket::func_sock_nothing(%s, %d)\n"
        , Socket::toAddr(*sa).c_str()
        , Socket::toPort(*sa)
    );
    return 0;
}

int32_t Socket::bind_wrap( int32_t fd, const sockaddr* sa, socklen_t sa_len, Socket* cls)
{
    return ::bind( fd, sa, sa_len );
}

int32_t Socket::connect_timeout_wrap(int32_t fd, const sockaddr* sa, socklen_t sa_len, Socket* cls)
{
#define RETURN_OUT(return_val, msg, args...) {\
ret = return_val; \
/*::fprintf(stderr, ""msg"", ##args);*/ \
goto out;}

///*::fprintf(stderr, ""msg"", ##args);*/ \
//::fprintf(stderr, ""msg"", ##args); \

    int32_t   ret       = 0;
    int32_t   error     = 0;
    socklen_t error_len = sizeof(error);
    int32_t   val_fcntl = 0;
    fd_set readfds, writefds;

    //设置监听socket为非阻塞
    val_fcntl = ::fcntl(fd, F_GETFL, 0); 
    ::fcntl(fd, F_SETFL, val_fcntl|O_NONBLOCK); 
    //初始化::select参数
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_SET(fd, &readfds);
    FD_SET(fd, &writefds);
    int res = ::connect(fd, sa, sa_len);
    //如果不能立即成功
    if ( res < 0 )
    {
        if ((errno != EINPROGRESS) && (errno != EWOULDBLOCK)) 
        {
            RETURN_OUT(-1, "connect failed!%s", strerror(errno) )
        }
        //尝试select监听可读可写
        else
        {
            while ( (res=::select(fd+1, &readfds, &writefds, NULL, &(cls->tm_conn)) ) <= 0 )
            {
                if(res<0)
                {  
                    if(EINTR == errno) continue;
                    RETURN_OUT(-2, "select failed!%s", strerror(errno) )
                }
                else//res==0
                {
                    RETURN_OUT(-3, "select timeout!")
                }
                break;
            }
        }
    }
    //client和server在同一台机器上
    //可能直接成功
    else if ( res == 0 ){
        RETURN_OUT(0, "success")
    }

    if ( FD_ISSET(fd, &readfds) || FD_ISSET(fd, &writefds) )
    {
        if ( ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_len) < 0 )
        {
            RETURN_OUT(-1, "connect fail1! %s", strerror(errno))
        }
    }
    if ( error )
    {
        errno = error;
        RETURN_OUT(-1, "connect fail2! %s", strerror(errno))
    }
    ret=0;
out:
    fcntl(fd, F_SETFL, val_fcntl); //清除非阻塞设置
    return ret;
#undef RETURN_OUT
}



};//namespace MINIKIT

